﻿// ****************************************************************************
// <copyright file="WeakFuncGeneric.cs" company="GalaSoft Laurent Bugnion">
// Copyright © GalaSoft Laurent Bugnion 2012-2013
// </copyright>
// ****************************************************************************
// <author>Laurent Bugnion</author>
// <email>laurent@galasoft.ch</email>
// <date>15.1.2012</date>
// <project>GalaSoft.MvvmLight</project>
// <web>http://www.galasoft.ch</web>
// <license>
// See license.txt in this solution or http://www.galasoft.ch/license_MIT.txt
// </license>
// ****************************************************************************
using System;
using System.Diagnostics.CodeAnalysis;

namespace TimersXP.Helpers
{
    /// <summary>Stores a Function without causing a hard reference to be created to the Function's owner. The owner can be garbage collected at any time.</summary>
    /// <typeparam name="T">The type of the Function's parameter.</typeparam>
    /// <typeparam name="TResult">The type of the Function's return value.</typeparam>
    public class WeakFunction<T, TResult> : WeakFunction<TResult>, IExecuteWithObjectAndResult
    {
        #region Class Variables
#if SILVERLIGHT
        /// <summary>The function</summary>
        private Func<T, TResult> function;
#endif
        /// <summary>The static function</summary>
        private Func<T, TResult> staticFunction;
        #endregion Class Variables

        #region Properties
        /// <summary>Gets or sets the name of the method that this WeakFunction represents.</summary>
        /// <value>The name of the method.</value>
        public override string MethodName
        {
            get
            {
                if (staticFunction != null)
                {
#if NETFX_CORE
                    return staticFunction.GetMethodInfo().Name;
#else
                    return staticFunction.Method.Name;
#endif
                }

#if SILVERLIGHT
                if (function != null)
                    return function.Method.Name;

                if (Method != null)
                    return Method.Name;

                return string.Empty
#else
                return Method.Name;
#endif
            }
        }

        /// <summary>Gets a value indicating whether the Function's owner [is alive] still, or if it was collected by the Garbage Collector already.</summary>
        /// <value><c>true</c> if [is alive]; otherwise, <c>false</c>.</value>
        public override bool IsAlive
        {
            get
            {
                if (staticFunction == null && Reference == null)
                    return false;

                if (staticFunction != null)
                {
                    if (Reference != null)
                        return Reference.IsAlive;

                    return true;
                }
                return Reference.IsAlive;
            }
        }
        #endregion Properties

        #region Constructors
        /// <summary>Initializes a new instance of the <see cref="WeakFunction{T, TResult}"/> class.</summary>
        /// <param name="function">The function that will be associated to this instance.</param>
        public WeakFunction(Func<T, TResult> function) : this(function == null ? null : function.Target, function) { }

        /// <summary>Initializes a new instance of the <see cref="WeakFunction{T, TResult}"/> class.</summary>
        /// <param name="target">The Function's owner.</param>
        /// <param name="function">The Function that will be associated to this instance.</param>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Method should fail with an exception if function is null")]
        public WeakFunction(object target, Func<T, TResult> function)
        {
#if NETFX_CORE
            if (function.GetMethodInfo().IsStatic)
#else
            if (function.Method.IsStatic)
#endif
            {
                this.staticFunction = function;
                if (target != null)
                {
                    // Keep a referenc to the target to control the WeakAction's lifetime.
                    Reference = new WeakReference(target);
                }
                return;
            }

#if SILVERLIGHT
            if (!function.Method.IsPublic || (target != null && !target.GetType().IsPublic && !target.GetType().IsNestedPublic))
                this.function = function;
            else
            {
                var name = function.Method.Name;
                if (name.Contains("<") && name.Contains(">"))
                    this.function = function;
                else
                {
                    Method = function.Method;
                    FunctionReference = new WeakReference(function.Target);
                }
            }
#else
#if NETFX_CORE
            Method = function.GetMethodInfo()
#else
            Method = function.Method;
#endif
            FunctionReference = new WeakReference(function.Target);
#endif
            Reference = new WeakReference(target);
        }
        #endregion Constructors

        #region Public Functions
        /// <summary>Executes the Function. This only happens if the Function's owner is still alive. The Function's parameter is set to default(T).</summary>
        /// <returns>The result of the Function stored as reference.</returns>
        public new TResult Execute() { return Execute(default(T)); }

        /// <summary>Executes the Function. This only happens if the Function's owner is still alive.</summary>
        /// <param name="parameter">A parameter to be passed to the action.</param>
        /// <returns>The result of the Function stored as reference.</returns>
        public TResult Execute(T parameter)
        {
            if (this.staticFunction != null)
                return this.staticFunction(parameter);

            var functionTarget = FunctionTarget;
            if (IsAlive)
            {
                if (Method != null && FunctionReference != null && functionTarget != null)
                    return (TResult)Method.Invoke(functionTarget, new object[] { parameter });

#if SILVERLIGHT
                if (this.function != null)
                    return this.function(parameter);
#endif
            }
            return default(TResult);
        }

        /// <summary>
        /// Executes the Function with a parameter of type object. This parameter will be casted to T. This method implements <see cref="IExecuteWithObject.ExecuteWithObject"/>
        /// and can be useful if you store multiple WeakFunction{T} instances but don't know in advance what type T represents.
        /// </summary>
        /// <param name="parameter">The parameter that will be passed to the Function after being casted to the appropriate type T.</param>
        /// <returns>The result of the execution as object, to be casted to T.</returns>
        public object ExecuteWithObject(object parameter)
        {
            var parameterCasted = (T)parameter;
            return Execute(parameterCasted);
        }

        /// <summary>Sets all the Functions that this WeakFunction contains to null, which is a signal for containing objects that this WeakFunction should be deleted.</summary>
        public new void MarkForDeletion()
        {
#if SILVERLIGHT
            this.function = null;
#endif
            this.staticFunction = null;
            base.MarkForDeletion();
        }
        #endregion Public Functions
    }
}
